Skip to content

feat(select): add labelPlacement prop, justify prop, and rich content styles for select options#31138

Open
thetaPC wants to merge 67 commits into
nextfrom
FW-7290
Open

feat(select): add labelPlacement prop, justify prop, and rich content styles for select options#31138
thetaPC wants to merge 67 commits into
nextfrom
FW-7290

Conversation

@thetaPC

@thetaPC thetaPC commented May 12, 2026

Copy link
Copy Markdown
Contributor

Issue number: resolves internal


What is the current behavior?

  • ion-select-option doesn't expose labelPlacement or justify props
  • The ionic theme's overlay rendering doesn't match the latest design
  • <ion-icon> placed inside an ion-select-option renders empty in Vue/React (and other frameworks that bind custom-element props as DOM properties)
  • Slotted children in select-option-start / select-option-end aren't size-capped consistently: oversized content
  • Several theme style files (alert.{ios,md,ionic}.scss, action-sheet.ionic.scss, select-{modal,popover}.{ios,md,ionic}.scss) import the wrong shared partials

What is the new behavior?

  • Add labelPlacement and justify props to ion-select-option and pass them through to alert, popover, and modal overlay paths
  • Update the ionic theme's alert, popover, and modal overlay styles to match the current design
  • Add per-theme slot-size limitations so oversized slotted content is capped to keep option rows visually uniform within an overlay
  • Audit and correct the @use / @import chains in the affected theme stylesheets so each theme file pulls in the right shared partials (mixins, theme globals, common base) and no longer relies on transitive imports that weren't guaranteed
  • Add reflectPropertiesToAttributes to core/src/utils/sanitization/ and call it from getOptionContent immediately before cloneNode. The helper mirrors a registered set of custom-element DOM properties (icon, name, src, ios, md on ion-icon) onto attributes so the cloned overlay copy renders correctly regardless of how the framework bound the prop.
  • Add test pages and Playwright snapshots for the overlays

Does this introduce a breaking change?

  • Yes
  • No

Other information

Dev build: 8.8.9-dev.11779411201.1a483e09

Preview:

@vercel

vercel Bot commented May 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ionic-framework Ready Ready Preview, Comment Jun 8, 2026 10:22pm

Request Review

height: 40px;
}

// Icon

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since MD does not have an official select option, I determined the values based on the list component it has.

Icons are considered to be a supported, the size can be found under "Tokens & specs > Token > Size and typography".

height: 24px;
}

// Image / SVG / Thumbnail

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since MD does not have an official select option, I determined the values based on the list component it has.

Images are considered to be a supported, the size can be found under "Tokens & specs > Token > Size and typography".

height: 56px;
}

// Video

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since MD does not have an official select option, I determined the values based on the list component it has.

Videos are considered to be a supported, the size can be found under "Tokens & specs > Token > Size and typography".

@thetaPC thetaPC requested a review from brandyscarney June 2, 2026 21:22
@thetaPC thetaPC changed the title feat(many): add rich content styles for select options feat(many): add labelPlacement prop, justify prop, and rich content styles for select options Jun 3, 2026
Comment thread core/src/components/select-option/select-option.ionic.interface.scss Outdated
Comment thread core/src/components/alert/alert.md.vars.scss Outdated
Comment thread core/src/components/select-option/select-option.common.overlay.scss
Comment thread core/src/components/select-option/select-option.ios.interface.scss Outdated
Comment thread core/api.txt
Comment thread core/src/components/alert/alert.tsx
@thetaPC thetaPC changed the title feat(many): add labelPlacement prop, justify prop, and rich content styles for select options feat(select): add labelPlacement prop, justify prop, and rich content styles for select options Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Values are based on MD2 specs for dialogs.

@brandyscarney brandyscarney left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work!! Some subtle changes in here but they make a huge difference. I love it! 🎉

Comment thread core/src/components/select-option/select-option.ionic.overlay.scss
Comment thread core/src/components/select-option/select-option.ios.overlay.scss
Comment thread core/src/components/select-option/select-option.md.overlay.scss
Comment thread core/src/components/select-option/select-option.native.overlay.scss

@ShaneK ShaneK left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking awesome! I just had some security-related concerns and a couple of nits. Let me know what you think

Comment thread core/src/utils/sanitization/index.ts Outdated

// strip event-handler attributes (already removed by the allowlist
// when !allowSafeAttributes; this guards the permissive path)
if (lowerName.startsWith('on')) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old rich-content path ran through sanitizeDOMString, whose allowlist kept only class/id/href/src/name/slot. sanitizeDOMTree's permissive mode flips that to an everything-except-denylist: it keeps any attribute that isn't on*-prefixed or javascript:-valued. So style (CSS injection, background:url() beaconing, UI spoofing), data: URIs in href/src, formaction/action/target, and xlink:href on SVG <a> now survive where they were always stripped before. The javascript: guard also leans on element[attributeName] property reflection to catch entity-obfuscated payloads, and that's undefined for namespaced attrs, so something like java&#9;script: in xlink:href would only hit the literal-substring value check and slip through.

I know this path is opt-in (innerHTMLTemplatesEnabled defaults to false) and the content is the developer's own ion-select-option markup, not raw user input, so the real-world exposure is narrow. And I get that the goal is to keep size/color/shape. Mostly I want a conscious sign-off here: would keeping an allowlist even in permissive mode (or at minimum stripping style and normalizing the URL-bearing attrs) be safer than the denylist? Not blocking, but worth a deliberate call since it widens what reaches the innerHTML/VNode sinks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}}
></Tag>
<Tag class={className} key={keyPrefix}>
{Array.from(content.childNodes).map((child, i) => cloneToVNode(child, keyPrefix, i))}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cloneToVNode filters nothing by design and the doc leans on callers to pre-sanitize. The ion-select path is fine since getOptionContent runs sanitizeDOMTree first. The gap is renderOptionLabel: it accepts label: string | HTMLElement and reads startContent/endContent, none of which are on the public AlertInput type. A vanilla-JS consumer doing alertController.create({ inputs: [{ label: someEl, startContent: ... }] }) reaches cloneToVNode with DOM that never passed through sanitizeDOMTree, and nothing enforces the contract.

Could we call sanitizeDOMTree defensively inside renderClonedContent? It'd be idempotent for the already-sanitized select path, so it just closes the JS-consumer hole without changing the select behavior. Up to you though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread core/src/utils/overlay-control-label.ts Outdated
Comment thread core/src/utils/overlay-control-label.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: angular @ionic/angular package package: core @ionic/core package package: vue @ionic/vue package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants